package restful

import (
	"compress/gzip"
	"encoding/hex"
	"encoding/xml"
	"fmt"
	"io"
	"io/ioutil"
	"moss/api"
	"moss/conf"
	"moss/fs"
	"moss/meta"
	"moss/status"
	"net/http"
)

func loadAndValidateXmlDocument(instance interface{}, req *http.Request) status.Status {
	body, e := ioutil.ReadAll(req.Body)
	if e != nil || len(body) == 0 {
		return status.MalformedXML
	}

	contentMD5 := req.Header.Get(conf.HTTPHeaderContentMD5)
	if len(contentMD5) != 0 && calculateMD5(body) != contentMD5 {
		return status.InvalidDigest
	}

	e = xml.Unmarshal(body, &instance)
	if e != nil {
		return status.MalformedXML
	}

	return status.Success
}

func loadXmlDocument(instance interface{}, req *http.Request) status.Status {
	body, e := ioutil.ReadAll(req.Body)
	if e != nil || len(body) == 0 {
		return status.MalformedXML
	}

	e = xml.Unmarshal(body, &instance)
	if e != nil {
		return status.MalformedXML
	}

	return status.Success
}

func sendXmlDocument(object interface{}, httpStatusCode int, requestId string, w http.ResponseWriter, headers map[string]string) status.Status {
	marshalBytes, e := xml.Marshal(object)
	if e != nil {
		return status.InternalError
	}
	w.Header().Set(conf.HTTPHeaderOssRequestID, requestId)
	w.Header().Set(conf.HTTPHeaderContentType, conf.MIMEIdentifierXML)
	addHeaderOptions(w, headers)
	w.WriteHeader(httpStatusCode)
	_, e = w.Write(marshalBytes)
	if e != nil {
		return status.InternalError
	}

	_, e = w.Write([]byte("\r\n"))
	if e != nil {
		return status.InternalError
	}

	return status.Success
}

func sendSuccess(requestId string, w http.ResponseWriter, headers map[string]string) {
	w.Header().Set(conf.HTTPHeaderOssRequestID, requestId)
	addHeaderOptions(w, headers)
	w.WriteHeader(http.StatusOK)
}

func sendHeaderOnly(requestId string, w http.ResponseWriter, headers map[string]string) {
	w.Header().Set(conf.HTTPHeaderOssRequestID, requestId)
	addHeaderOptions(w, headers)
}

func sendNoContent(requestId string, w http.ResponseWriter, headers map[string]string) {
	w.Header().Set(conf.HTTPHeaderOssRequestID, requestId)
	addHeaderOptions(w, headers)
	w.WriteHeader(http.StatusNoContent)
}

func sendHttpStatus(requestId string, w http.ResponseWriter, code int, headers map[string]string) {
	w.Header().Set(conf.HTTPHeaderOssRequestID, requestId)
	addHeaderOptions(w, headers)
	w.WriteHeader(code)
}

func sendStream(w io.Writer, r io.ReadSeeker, size int64, offset int64, encryption string, enableGzip bool) status.Status {
	_, err := r.Seek(offset, io.SeekStart)
	if err != nil {
		println(err)
		return status.InternalError
	}

	var dst io.Writer
	var src io.Reader

	if enableGzip {
		dst = gzip.NewWriter(w)
	} else {
		dst = w
	}

	if encryption == "AES256" {
		privateKey, _ := hex.DecodeString(conf.Config.Security.AesPrivateKey)
		src = fs.EncryptReader(r, privateKey)
	} else {
		src = r
	}

	_, err = io.CopyN(dst, src, size)
	if err != nil {
		println(err)
		return status.InternalError
	}

	if enableGzip {
		dst.(io.WriteCloser).Close()
	}

	return status.Success
}

func sendObjectContent(w http.ResponseWriter, req *http.Request, objectInfo *meta.Object, requestId string,
	httpStatus int, sendRange string) {
	hasRangeOption, startIdx, stopIdx := praseRangeString(sendRange)
	if stopIdx >= objectInfo.Size {
		hasRangeOption = false
	}

	contentRange := ""
	size := objectInfo.Size
	offset := int64(0)
	if hasRangeOption {
		contentRange = fmt.Sprintf("bytes %d-%d/%d", startIdx, stopIdx, objectInfo.Size)
		size = stopIdx + 1 - startIdx
		offset = startIdx
	}
	filename := objectInfo.FileLocation
	doc := fs.SafeOpenFile(filename, objectInfo.Size)
	if doc == nil {
		reportError(w, req, status.InternalError, requestId)
		return
	}
	defer doc.Close()
	etag := objectInfo.ETag
	enableGZip := false // hasGzip(req)
	if enableGZip {
		etag = ""
		addHeaderOptions(w, map[string]string{conf.HTTPHeaderContentEncoding: "gzip"})
	}

	addHeaderOptions(w, map[string]string{conf.HTTPHeaderContentRange: contentRange, conf.HTTPHeaderEtag: etag})
	addHeaderOptions(w, map[string]string{conf.HTTPHeaderOssRequestID: requestId})
	if len(objectInfo.Encryption) != 0 {
		addHeaderOptions(w, map[string]string{conf.HTTPHeaderOssServerSideEncryption: objectInfo.Encryption})
	}
	headers := setUserMeta(objectInfo.UserMeta)
	addHeaderOptions(w, headers)
	w.WriteHeader(httpStatus)

	sendStream(w, doc, size, offset, objectInfo.Encryption, enableGZip)
}

func sendWebsiteContent(w http.ResponseWriter, req *http.Request, requestId string) status.Status {
	objectInfo, rc := api.GetWebsiteContent(getBucketName(req), req.URL)
	if rc != status.Success {
		if rc == status.NoSuchWebsiteConfiguration {
			// Hack to user: wrong authorization parameter
			rc = status.InvalidArgument
		}

		if objectInfo == nil {
			reportError(w, req, rc, requestId)
			return rc
		} else {
			sendObjectContent(w, req, objectInfo, requestId, http.StatusNotFound, "")
			return rc
		}
	} else {
		sendObjectContent(w, req, objectInfo, requestId, http.StatusOK, "")
		return rc
	}
}
